winsafe\kernel\utilities/path.rs
1//! File path utilities.
2//!
3//! Some of the functions are similar to [`std::path::Path`] ones, but here they
4//! work directly upon [`&str`](str) instead of [`&OsStr`](std::ffi::OsStr).
5
6use crate::co;
7use crate::decl::*;
8use crate::kernel::iterators::*;
9use crate::prelude::*;
10
11/// Returns an iterator over the files and folders within a directory.
12/// Optionally, a wildcard can be specified to filter files by name.
13///
14/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
15/// iteration functions.
16///
17/// # Examples
18///
19/// Listing all text files in a directory:
20///
21/// ```no_run
22/// use winsafe::{self as w, prelude::*};
23///
24/// for file_path in w::path::dir_list("C:\\temp", Some("*.txt")) {
25/// let file_path = file_path?;
26/// println!("{}", file_path);
27/// }
28/// # w::SysResult::Ok(())
29/// ```
30#[must_use]
31pub fn dir_list<'a>(
32 dir_path: &'a str,
33 filter: Option<&'a str>,
34) -> impl Iterator<Item = SysResult<String>> + 'a {
35 DirListIter::new(dir_path.to_owned(), filter)
36}
37
38/// Returns an interator over the files within a directory, and all its
39/// subdirectories, recursively.
40///
41/// This is a high-level abstraction over [`HFINDFILE`](crate::HFINDFILE)
42/// iteration functions.
43///
44/// # Examples
45///
46/// ```no_run
47/// use winsafe::{self as w, prelude::*};
48///
49/// // Ordinary for loop
50/// for file_path in w::path::dir_walk("C:\\Temp") {
51/// let file_path = file_path?;
52/// println!("{}", file_path);
53/// }
54///
55/// // Closure with try_for_each
56/// w::path::dir_walk("C:\\Temp")
57/// .try_for_each(|file_path| {
58/// let file_path = file_path?;
59/// println!("{}", file_path);
60/// Ok(())
61/// })?;
62///
63/// // Collecting into a Vec
64/// let all = w::path::dir_walk("C:\\Temp")
65/// .collect::<w::SysResult<Vec<_>>>()?;
66///
67/// // Transforming and collecting into a Vec
68/// let all = w::path::dir_walk("C:\\Temp")
69/// .map(|file_path| {
70/// let file_path = file_path?;
71/// Ok(format!("PATH: {}", file_path))
72/// })
73/// .collect::<w::SysResult<Vec<_>>>()?;
74/// # w::SysResult::Ok(())
75/// ```
76#[must_use]
77pub fn dir_walk<'a>(dir_path: &'a str) -> impl Iterator<Item = SysResult<String>> + 'a {
78 DirWalkIter::new(dir_path.to_owned())
79}
80
81/// Returns a new string with the path of the current EXE file, without the EXE
82/// filename, and without a trailing backslash.
83///
84/// In a debug build, the `target\debug` folders will be suppressed.
85#[cfg(debug_assertions)]
86#[must_use]
87pub fn exe_path() -> SysResult<String> {
88 let dbg = HINSTANCE::NULL.GetModuleFileName()?;
89 Ok(get_path(
90 get_path(
91 get_path(&dbg).unwrap(), // exe name; go up target and debug folders
92 )
93 .unwrap(),
94 )
95 .unwrap()
96 .to_owned())
97}
98
99/// Returns a new string with the path of the current EXE file, without the EXE
100/// filename, and without a trailing backslash.
101///
102/// In a debug build, the `target\debug` folders will be suppressed.
103#[cfg(not(debug_assertions))]
104#[must_use]
105pub fn exe_path() -> SysResult<String> {
106 Ok(get_path(&HINSTANCE::NULL.GetModuleFileName()?)
107 .unwrap()
108 .to_owned())
109}
110
111/// Returns true if the path exists.
112#[must_use]
113pub fn exists(full_path: &str) -> bool {
114 GetFileAttributes(full_path).is_ok()
115}
116
117/// Extracts the file name from a full path, if any.
118///
119/// # Examples
120///
121/// ```no_run
122/// use winsafe::{self as w, prelude::*};
123///
124/// let f = w::path::get_file_name("C:\\Temp\\foo.txt"); // foo.txt
125/// ```
126#[must_use]
127pub fn get_file_name(full_path: &str) -> Option<&str> {
128 match full_path.rfind('\\') {
129 None => Some(full_path), // if no backslash, the whole string is the file name
130 Some(idx) => {
131 if idx == full_path.chars().count() - 1 {
132 None // last char is '\\', no file name
133 } else {
134 Some(&full_path[idx + 1..])
135 }
136 },
137 }
138}
139
140/// Extracts the full path, but the last part.
141///
142/// # Examples
143///
144/// ```no_run
145/// use winsafe::{self as w, prelude::*};
146///
147/// let p = w::path::get_path("C:\\Temp\\xx\\a.txt"); // C:\Temp\xx
148/// let q = w::path::get_path("C:\\Temp\\xx\\"); // C:\Temp\xx
149/// let r = w::path::get_path("C:\\Temp\\xx"); // C:\Temp"
150/// ```
151#[must_use]
152pub fn get_path(full_path: &str) -> Option<&str> {
153 full_path
154 .rfind('\\') // if no backslash, the whole string is the file name, so no path
155 .map(|idx| &full_path[0..idx])
156}
157
158/// Tells whether the full path ends in one of the given extensions,
159/// case-insensitive.
160///
161/// # Examples
162///
163/// ```no_run
164/// use winsafe::{self as w, prelude::*};
165///
166/// println!("{}",
167/// w::path::has_extension("file.txt", &[".txt", ".bat"]));
168/// ```
169#[must_use]
170pub fn has_extension(full_path: &str, extensions: &[impl AsRef<str>]) -> bool {
171 let full_path_u = full_path.to_uppercase();
172 extensions
173 .iter()
174 .find(|ext| {
175 let ext_u = ext.as_ref().to_uppercase();
176 full_path_u.ends_with(&ext_u)
177 })
178 .is_some()
179}
180
181/// Returns true if the path is a directory. Calls
182/// [`GetFileAttributes`](crate::GetFileAttributes).
183///
184/// # Panics
185///
186/// Panics if the path does not exist.
187#[must_use]
188pub fn is_directory(full_path: &str) -> bool {
189 let flags = GetFileAttributes(full_path).unwrap();
190 flags.has(co::FILE_ATTRIBUTE::DIRECTORY)
191}
192
193/// Returns true if the path is hidden. Calls
194/// [`GetFileAttributes`](crate::GetFileAttributes).
195///
196/// # Panics
197///
198/// Panics if the path does not exist.
199#[must_use]
200pub fn is_hidden(full_path: &str) -> bool {
201 let flags = GetFileAttributes(full_path).unwrap();
202 flags.has(co::FILE_ATTRIBUTE::HIDDEN)
203}
204
205/// Replaces the file extension by the given one, returning a new string.
206///
207/// # Examples
208///
209/// ```no_run
210/// use winsafe::{self as w, prelude::*};
211///
212/// let p = w::path::replace_extension(
213/// "C:\\Temp\\something.txt", ".sh"); // C:\Temp\something.sh
214/// ```
215#[must_use]
216pub fn replace_extension(full_path: &str, new_extension: &str) -> String {
217 if let Some(last) = full_path.chars().last() {
218 if last == '\\' {
219 return rtrim_backslash(full_path).to_owned(); // full_path is a directory, do nothing
220 }
221 }
222
223 let new_has_dot = new_extension.chars().next() == Some('.');
224 match full_path.rfind('.') {
225 None => format!(
226 "{}{}{}", // file name without extension, just append it
227 full_path,
228 if new_has_dot { "" } else { "." },
229 new_extension,
230 ),
231 Some(idx) => {
232 format!("{}{}{}", &full_path[0..idx], if new_has_dot { "" } else { "." }, new_extension,)
233 },
234 }
235}
236
237/// Replaces the file name by the given one, returning a new string.
238#[must_use]
239pub fn replace_file_name(full_path: &str, new_file: &str) -> String {
240 match get_path(full_path) {
241 None => new_file.to_owned(),
242 Some(path) => format!("{}\\{}", path, new_file),
243 }
244}
245
246/// Keeps the file name and replaces the path by the given one, returning a new
247/// string.
248///
249/// # Examples
250///
251/// ```no_run
252/// use winsafe::{self as w, prelude::*};
253///
254/// let p = w::path::replace_path( // C:\another\foo.txt
255/// "C:\\Temp\\foo.txt",
256/// "C:\\another",
257/// );
258/// ```
259#[must_use]
260pub fn replace_path(full_path: &str, new_path: &str) -> String {
261 let file_name = get_file_name(full_path);
262 format!(
263 "{}{}{}",
264 rtrim_backslash(new_path),
265 if file_name.is_some() { "\\" } else { "" },
266 file_name.unwrap_or("")
267 )
268}
269
270/// Removes a trailing backslash, if any.
271///
272/// # Examples
273///
274/// ```no_run
275/// use winsafe::{self as w, prelude::*};
276///
277/// let p = w::path::rtrim_backslash("C:\\Temp\\"); // C:\Temp
278/// ```
279#[must_use]
280pub fn rtrim_backslash(full_path: &str) -> &str {
281 match full_path.chars().last() {
282 None => full_path, // empty string
283 Some(last_ch) => {
284 if last_ch == '\\' {
285 let mut chars = full_path.chars();
286 chars.next_back(); // remove last char
287 chars.as_str()
288 } else {
289 full_path // no trailing backslash
290 }
291 },
292 }
293}
294
295/// Returns a `Vec` with each part of the full path.
296#[must_use]
297pub fn split_parts(full_path: &str) -> Vec<&str> {
298 let no_bs = rtrim_backslash(full_path);
299 no_bs.split('\\').collect()
300}